package jane.mvc.engine;

import jane.mvc.actionresults.IActionResult;
import jane.mvc.controller.IController;
import jane.mvc.exceptions.ControllerNotFoundException;
import jane.mvc.utils.MyLog;
import jane.mvc.utils.Utils;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;

public class JaneMVCHandler extends AbstractHandler
{

	private Application application = null;

	public JaneMVCHandler(Application application)
	{
		this.application = application;
	}

	public void handle(String target, Request baseRequest,
			HttpServletRequest request, HttpServletResponse response)
			throws IOException, ServletException
	{

		JaneMVCContext context = new JaneMVCContext(target, baseRequest,
				request, response);

		AppDescriptor appdescriptor = application.getAppDescriptor();

		String pathinfo = request.getPathInfo().toLowerCase();

		if (pathinfo.equals("/"))
		{
			handleRoot(context);
			return;
		} else if (appdescriptor.getMethodsByWebname().containsKey(pathinfo))
		{

			JaneMVCMethod method = appdescriptor.getMethodsByWebname().get(
					pathinfo);

			Map<String, String[]> paramMap = request.getParameterMap();

			runMethod(context, method, paramMap);
		}

		// If it reaches this point, there's no method to respond to the
		// request.
		send404(context);
	}

	private void send404(JaneMVCContext context)
	{
		// TODO: implement personalized 404.

		context.getResponse().setStatus(404);
	}

	private void handleRoot(JaneMVCContext context)
	{
		String homeControllerName = null;

		try
		{
			homeControllerName = application.getAppDescriptor()
					.getHomeController();
		} catch (ControllerNotFoundException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		if (homeControllerName != null)
		{

			IController controller = initializeController(context,
					homeControllerName);

			runIndex(context, controller);

		}
	}

	private void runMethod(JaneMVCContext context, JaneMVCMethod method,
			Map<String, String[]> paramMap)
	{
		Method executableMethod;

		IController controller = initializeController(context,
				method.getControllerName());

		Class controllerClass = controller.getClass();

		try
		{

			// MyLog.info(method.getParamsNamesList().toString());

			executableMethod = controllerClass.getMethod(method.getMethod()
					.getName(), method.getMethod().getParameterTypes());

			executableMethod.setAccessible(true);

			Class<?>[] paramtypes = executableMethod.getParameterTypes();

			ArrayList<Object> argumentsToMethod = new ArrayList<Object>();

			if (paramtypes.length == method.getParamsNamesList().size())
			{

				ArrayList<Object> metargs = new ArrayList<Object>();

				for (int i = 0; i < paramtypes.length; i++)
				{

					String paramName = method.getParamsNamesList().get(i);

					String[] paramValue = paramMap.get(paramName);

					if (paramValue.length == 1)
					{
						String paramValueSingle = paramValue[0];

						if (paramtypes[i].isPrimitive())
						{
							if ((paramtypes[i].equals(Boolean.TYPE)))
							{
								metargs.add(i,
										Boolean.parseBoolean(paramValueSingle));
							}

							if ((paramtypes[i].equals(Byte.TYPE)))
							{
								metargs.add(i, Byte.parseByte(paramValueSingle));
							}
							if ((paramtypes[i].equals(Character.TYPE)))
							{
								metargs.add(i, paramValueSingle.charAt(0));
							}

							if ((paramtypes[i].equals(Double.TYPE)))
							{
								metargs.add(i,
										Double.parseDouble(paramValueSingle));
							}
							if ((paramtypes[i].equals(Float.TYPE)))
							{
								metargs.add(i,
										Float.parseFloat(paramValueSingle));
							}

							if ((paramtypes[i].equals(Short.TYPE)))
							{
								metargs.add(i,
										Short.parseShort(paramValueSingle));
							}

							if ((paramtypes[i].equals(Integer.TYPE)))
							{
								metargs.add(i,
										Integer.parseInt(paramValueSingle));
							}

							if ((paramtypes[i].equals(Long.TYPE)))
							{
								metargs.add(i, Long.parseLong(paramValueSingle));
							}

						} else
						{
							// TODO: implement a map with all the permitted
							// types. By now, it only checks if it's a string.
							if (paramtypes[i].getName().equals(
									String.class.getName()))
							{
								metargs.add(i, paramValueSingle);
							}

						}
					}
				}

				MyLog.info(metargs.toString());

				IActionResult act = (IActionResult) executableMethod.invoke(
						controller, metargs.toArray());
				act.executeResult(context);

			} else
			{
				IActionResult act = (IActionResult) executableMethod.invoke(
						controller, argumentsToMethod.toArray());
				act.executeResult(context);

			}

		} catch (SecurityException | IllegalArgumentException e1)
		{
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (NoSuchMethodException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void runIndex(JaneMVCContext context, IController controller)
	{
		Method indexMethod;
		Class controllerClass = controller.getClass();

		try
		{

			indexMethod = controllerClass.getDeclaredMethod("Index");
			indexMethod.setAccessible(true);

			IActionResult act = (IActionResult) indexMethod.invoke(controller,
					new Object[] {});

			act.executeResult(context);

		} catch (NoSuchMethodException | SecurityException
				| IllegalAccessException | IllegalArgumentException
				| InvocationTargetException e1)
		{
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
	}

	private IController initializeController(JaneMVCContext context,
			String controllername)
	{
		Class controllerClass = new Utils().getClassFromJarByName(
				controllername, application.getAppDescriptor().getJarFile());

		IController controller = null;
		try
		{
			controller = (IController) controllerClass.newInstance();

		} catch (InstantiationException | IllegalAccessException e1)
		{
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		controller.setContext(context);

		Method initControllerMethod;

		try
		{
			initControllerMethod = controllerClass.getMethod("initController");
			initControllerMethod.setAccessible(true);
			initControllerMethod.invoke(controller, new Object[] {});
		} catch (NoSuchMethodException | SecurityException
				| IllegalAccessException | IllegalArgumentException
				| InvocationTargetException e1)
		{
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		return controller;
	}
}